/*
-- IOTA Crypto Core
--
-- 2018 by Thomas Pototschnig <microengineer18@gmail.com>
-- discord: pmaxuw#8292
-- https://gitlab.com/iccfpga-rv
--
-- Permission is hereby granted, free of charge, to any person obtaining
-- a copy of this software and associated documentation files (the
-- "Software"), to deal in the Software without restriction, including
-- without limitation the rights to use, copy, modify, merge, publish,
-- distribute, sublicense, and/or sell copies of the Software, and to
-- permit persons to whom the Software is furnished to do so, subject to
-- the following conditions:
-- 
-- The above copyright notice and this permission notice shall be
-- included in all copies or substantial portions of the Software.
-- 
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWAR

Base: https://github.com/IOTA-Ledger/blue-app-iota

*/

#include <fpga/conversion_fpga.h>

#include <string.h>
#include "fpga/fpga.h"

#define BIGINT_LENGTH 12

// chunk sizes
#define NUM_CHUNK_TRYTES (NUM_HASH_TRYTES)
#define NUM_CHUNK_BYTES (NUM_HASH_BYTES)

// base of the ternary system
#define BASE 3

// base of the ternary system represented in bytes
#define TRYTE_BASE 27


#define FPGA_BIGINT2TRITS	(1<<0)
#define FPGA_STOP			(1<<1)
#define FPGA_TRITS2BIGINT	(1<<2)
#define FPGA_TRYTES			(1<<31)
#define FPGA_RUNNING		(1<<0)

#define FPGA_REG_FLAGS		0x01
#define FPGA_REG_CTR		0x02

// the middle of the domain described by 242 trits, i.e. \sum_{k=0}^{241} 3^k
static const uint32_t HALF_3[12] = {
    0xa5ce8964, 0x9f007669, 0x1484504f, 0x3ade00d9, 0x0c24486e, 0x50979d57,
    0x79a4c702, 0x48bbae36, 0xa9f6808b, 0xaa06a805, 0xa87fabdf, 0x5e69ebef};

// the two's complement of HALF_3_u, i.e. ~HALF_3_u + 1
static const uint32_t NEG_HALF_3[12] = {
    0x5a31769c, 0x60ff8996, 0xeb7bafb0, 0xc521ff26, 0xf3dbb791, 0xaf6862a8,
    0x865b38fd, 0xb74451c9, 0x56097f74, 0x55f957fa, 0x57805420, 0xa1961410};

inline uint32_t ConversionFPGA::converterReadCtr() {
	return fpga_read_address(FPGA_REG_CTR);
}

inline void ConversionFPGA::converterUploadTrits(const trit_t* trits) {
	memcpy((void*) FPGA::converterGetTritsPtr(), trits, 243);
}

inline void ConversionFPGA::converterUploadTrytes(const tryte_t* trytes) {
	memcpy((void*) FPGA::converterGetTritsPtr(), trytes, 81);
}

inline void ConversionFPGA::converterUploadBigint(const uint32_t* bigint) {
	memcpy((void*) FPGA::converterGetBigintPtr(), bigint, 12 * sizeof(uint32_t));
}

inline void ConversionFPGA::converterDownloadTrits(trit_t* trits) {
	memcpy(trits, (void*) FPGA::converterGetTritsPtr(), 243);
}

inline void ConversionFPGA::converterDownloadTrytes(tryte_t* trytes) {
	memcpy(trytes, (void*) FPGA::converterGetTritsPtr(), 81);
}

inline void ConversionFPGA::converterDownloadBigint(uint32_t* bigint) {
	memcpy(bigint, (void*) FPGA::converterGetBigintPtr(), 12 * sizeof(uint32_t));
}

void ConversionFPGA::testClearData() {
	memset((void*) FPGA::converterGetTritsPtr(), 0, 243);
	memset((void*) FPGA::converterGetBigintPtr(), 0, 12 * sizeof(uint32_t));

}

/** @brief Returns true, if the long little-endian integer represents a negative
 *         number in two's complement.
 */
bool ConversionFPGA::bigint_is_negative(const uint32_t *bigint)
{
    // whether the most significant bit of the most significant byte is set
    return (bigint[12 - 1] >> (sizeof(bigint[0]) * 8 - 1) != 0);
}


void ConversionFPGA::trits_to_bigint(const trit_t *trits, uint32_t *bigint) {
	converterUploadTrits(trits);

	FPGA::converterWrite(FPGA_REG_FLAGS, FPGA_TRITS2BIGINT);

	while (FPGA::converterRead(FPGA_REG_FLAGS) & FPGA_RUNNING) {
		// wait
	}

	converterDownloadBigint(bigint);
	// convert to balanced ternary using two's complement
	if (bigint_cmp(bigint, HALF_3) >= 0) {
		bigint_sub(bigint, bigint, HALF_3);
	} else {
		// equivalent to bytes := ~(HALF_3 - bytes) + 1
		bigint_add(bigint, NEG_HALF_3, bigint);
	}
}

void ConversionFPGA::bigint_to_trits(uint32_t *bigint, trit_t *trits) {
	// the two's complement represention is only correct, if the number fits
	// into 48 bytes, i.e. has the 243th trit set to 0
	bigint_set_last_trit_zero(bigint);

    // convert to the (positive) number representing non-balanced ternary
    bigint_add(bigint, bigint, HALF_3);

	converterUploadBigint(bigint);

	FPGA::converterWrite(FPGA_REG_FLAGS, FPGA_BIGINT2TRITS);

	while (FPGA::converterRead(FPGA_REG_FLAGS) & FPGA_RUNNING) {
		// wait
	}

	converterDownloadTrits(trits);

	// convert to the (positive) number representing non-balanced ternary
	if (bigint_is_negative(bigint)) {
		bigint_sub(bigint, bigint, NEG_HALF_3);
	} else {
		bigint_add(bigint, bigint, HALF_3);
	}
	// set the last trit to zero for consistency
	trits[242] = 0;
}



void ConversionFPGA::trytes_to_bigint(const tryte_t *trytes, uint32_t *bigint)
{
	converterUploadTrytes(trytes);

	FPGA::converterWrite(FPGA_REG_FLAGS, FPGA_TRITS2BIGINT | FPGA_TRYTES);

    while (FPGA::converterRead(FPGA_REG_FLAGS) & FPGA_RUNNING) {
    	// wait
    }

    converterDownloadBigint(bigint);

    // substract the middle of the domain to get balanced ternary
    // as there cannot be any overflows with 242 trits, a simple substraction
    // yields the correct result in two's complement representation
    bigint_sub(bigint, bigint, HALF_3);
}

void ConversionFPGA::bigint_to_trytes_mem(uint32_t *bigint, tryte_t *trytes) {
	 // the two's complement represention is only correct, if the number fits
	// into 48 bytes, i.e. has the 243th trit set to 0
	bigint_set_last_trit_zero(bigint);

	// convert to the (positive) number representing non-balanced ternary
	bigint_add(bigint, bigint, HALF_3);

	converterUploadBigint(bigint);

	FPGA::converterWrite(FPGA_REG_FLAGS, FPGA_BIGINT2TRITS | FPGA_TRYTES);

	while (FPGA::converterRead(FPGA_REG_FLAGS) & FPGA_RUNNING) {
		// wait
	}

	converterDownloadTrytes(trytes);

	trytes[NUM_CHUNK_TRYTES - 1] = tryte_set_last_trit_zero(trytes[NUM_CHUNK_TRYTES - 1]);
}

void ConversionFPGA::trits_to_bytes(const trit_t *trits, unsigned char *bytes)
{
    uint32_t bigint[12];
    trits_to_bigint(trits, bigint);
    bigint_to_bytes(bigint, bytes);
}

void ConversionFPGA::bytes_to_trits(const unsigned char *bytes, trit_t *trits)
{
    uint32_t bigint[12];
    bytes_to_bigint(bytes, bigint);
    bigint_to_trits(bigint, trits);
}
